001    /**
002     * Java Gui Builder - A library to build GUIs using an XML file.
003     * Copyright 2002, 2003 (C) François Beausoleil
004     *
005     * This library is free software; you can redistribute it and/or
006     * modify it under the terms of the GNU Lesser General Public
007     * License as published by the Free Software Foundation; either
008     * version 2.1 of the License, or (at your option) any later version.
009     *
010     * This library is distributed in the hope that it will be useful,
011     * but WITHOUT ANY WARRANTY; without even the implied warranty of
012     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013     * Lesser General Public License for more details.
014     *
015     * You should have received a copy of the GNU Lesser General Public
016     * License along with this library; if not, write to the Free Software
017     * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
018     */
019    
020    package jgb.builder;
021    
022    import jgb.builder.utils.AttributesMapAdapter;
023    import jgb.factories.swing.PackageTagHandlerFactory;
024    import org.xml.sax.*;
025    import org.xml.sax.helpers.DefaultHandler;
026    
027    import javax.swing.*;
028    import javax.xml.parsers.ParserConfigurationException;
029    import javax.xml.parsers.SAXParser;
030    import javax.xml.parsers.SAXParserFactory;
031    import java.io.IOException;
032    import java.io.InputStream;
033    import java.io.PrintWriter;
034    import java.util.*;
035    import java.util.List;
036    import java.awt.*;
037    
038    
039    /**
040     * The class that builds the windows and controls on behalf of the application.
041     * This class instantiates the necessary objects while reading the XML source
042     * file.
043     * @since 0.2a
044     * @author Francois Beausoleil, <a href="mailto:fbos@users.sourceforge.net">fbos@users.sourceforge.net</a>
045     */
046    public class DefaultBuilder extends DefaultHandler implements Builder {
047        private Map tagContext = new HashMap();
048        private boolean quiet = false;
049    
050        private int level;
051        private boolean verbose = false;
052    
053        private final List resolvers = new LinkedList();
054        private PrintWriter logStream = new PrintWriter(System.out);
055    
056        /**
057         * Instantiate a <code>Builder</code> and use the default
058         * {@link jgb.builder.TagHandlerFactory TagHandlerFactory}.
059         * The default factory is {@link jgb.factories.swing.PackageTagHandlerFactory PackageTagHandlerFactory}.
060         */
061        public DefaultBuilder() {
062            this(new PackageTagHandlerFactory("jgb.handlers.swing", "TagHandler"));
063        }
064    
065        /**
066         * Instantiate a <code>Builder</code> with the specified
067         * {@link jgb.builder.TagHandlerFactory TagHandlerFactory}.
068         * The <code>Builder</code> uses this factory to get all of it's tag handlers.
069         * All tags must implement {@link jgb.builder.TagHandler TagHandler}.
070         */
071        public DefaultBuilder(final TagHandlerFactory tagsFactory) {
072            Stack handlersFactoryStack = new Stack();
073            handlersFactoryStack.push(tagsFactory);
074            tagContext.put(TagHandler.TAG_HANDLER_FACTORY_STACK_KEY, handlersFactoryStack);
075        }
076    
077        public void setQuiet(boolean quiet) {
078            this.quiet = quiet;
079        }
080    
081        public Component build(InputStream in) throws IOException, SAXException {
082            if (null == in) {
083                throw new IllegalArgumentException("in must not be null");
084            }
085    
086            return build(new InputSource(in));
087        }
088    
089        public Component build(InputSource in) throws IOException, SAXException {
090            if (null == in) {
091                throw new IllegalArgumentException("InputSource cannot be be null");
092            } else if (null == in.getByteStream()
093                    && null == in.getCharacterStream()
094                    && null == in.getSystemId()
095                    && null == in.getPublicId()) {
096                throw new IllegalArgumentException("InputSource should have at least one non-null source");
097            }
098    
099            SAXParserFactory factory = SAXParserFactory.newInstance();
100            factory.setValidating(true);
101            SAXParser parser = null;
102            try {
103                parser = factory.newSAXParser();
104            } catch (ParserConfigurationException e) {
105                throw new SAXException("No validating parser present on this system", e);
106            }
107    
108            tagContext.put(TagHandler.WINDOW_CONTEXT_KEY, this);
109            tagContext.put(TagHandler.OBJECTS_MAP_KEY, new HashMap());
110            parser.parse(in, this);
111            return (JWindow)tagContext.get(TagHandler.CURRENT_WINDOW_KEY);
112        }
113    
114        public void setLoggingStream(PrintWriter logStream) {
115            this.logStream = logStream;
116        }
117    
118        public Object getObject(String name) {
119            return (((Map)tagContext.get(TagHandler.OBJECTS_MAP_KEY)).get(name));
120        }
121    
122        /**
123         * Initiates processing of the given element.
124         * This method will use the factory to get a {@link jgb.builder.TagHandler TagHandler}
125         * to process this tag.<br />
126         * Tag specific operations may occur during this method call.
127         */
128        public void startElement(java.lang.String namespaceURI,
129                                 java.lang.String localName,
130                                 java.lang.String qName,
131                                 Attributes atts) throws SAXException {
132            if (verbose) {
133                logStream.print(levelAsSpaces() + "<" + qName);
134                if (null != atts.getValue("id")) {
135                    logStream.println(" id='" + atts.getValue("id") + "'>");
136                } else {
137                    logStream.println(">");
138                }
139                logStream.flush();
140            }
141    
142            TagHandler handler = getHandlerFor(qName);
143            handler.startElement(qName, tagContext, new AttributesMapAdapter(atts));
144    
145            level++;
146        }
147    
148        /**
149         * Terminates processing of the given element.
150         * This method will use the factory to get a {@link jgb.builder.TagHandler TagHandler}
151         * to process this tag.<br />
152         * Tag specific operations may occur during this method call.
153         */
154        public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
155            level--;
156            if (level < 0) {
157                level = 0;
158            }
159    
160            if (verbose) {
161                logStream.println(levelAsSpaces() + "</" + qName + ">");
162                logStream.flush();
163            }
164    
165            TagHandler handler = getHandlerFor(qName);
166            handler.endElement(qName, tagContext);
167        }
168    
169        /**
170         * Displays a warning message on the console, or no-op if in quiet mode.
171         */
172        public void warning(SAXParseException e) throws SAXException {
173            if (!quiet) {
174                logStream.print("WARNING: line: ");
175                logStream.print(e.getLineNumber());
176                logStream.print(", column: ");
177                logStream.print(e.getColumnNumber());
178                logStream.println(" - ");
179                logStream.print(e.getMessage());
180                logStream.print("(\"");
181                logStream.print(e.getPublicId());
182                logStream.print("\" \"");
183                logStream.print(e.getSystemId());
184                logStream.print("\")");
185                logStream.flush();
186            }
187        }
188    
189        /**
190         * Displays a fatal error message on the console, or no-op if in quiet mode.
191         * In all cases, it throws a <code>SAXException<code> to signify that processing
192         * should stop.
193         */
194        public void fatalError(SAXParseException e) throws SAXException {
195            if (!quiet) {
196                logStream.print("FATAL: line: ");
197                logStream.print(e.getLineNumber());
198                logStream.print(", column: ");
199                logStream.print(e.getColumnNumber());
200                logStream.println(" - ");
201                logStream.print(e.getMessage());
202                logStream.print("(\"");
203                logStream.print(e.getPublicId());
204                logStream.print("\" \"");
205                logStream.print(e.getSystemId());
206                logStream.print("\")");
207                logStream.flush();
208            }
209    
210            throw new SAXException("Fatal Error during parse", e);
211        }
212    
213        /**
214         * Displays an error message on the console, or no-op if in quiet mode.
215         * In all cases, it throws a <code>SAXException</code> to signify that processing
216         * should stop.
217         */
218        public void error(SAXParseException e) throws SAXException {
219            if (!quiet) {
220                logStream.print("ERROR: line: ");
221                logStream.print(e.getLineNumber());
222                logStream.print(", column: ");
223                logStream.print(e.getColumnNumber());
224                logStream.println(" - ");
225                logStream.print(e.getMessage());
226                logStream.print("(\"");
227                logStream.print(e.getPublicId());
228                logStream.print("\" \"");
229                logStream.print(e.getSystemId());
230                logStream.print("\")");
231                logStream.flush();
232            }
233    
234            throw new SAXException("Error during parse", e);
235        }
236    
237        /**
238         * Saves the document locator that the parser might provide.  If it does so,
239         * we are in business, else, we're toast...
240         */
241        public void setDocumentLocator(Locator locator) {
242            tagContext.put(TagHandler.DOCUMENT_LOCATOR_KEY, locator);
243            super.setDocumentLocator(locator);
244        }
245    
246        public void setVerbose(boolean b) {
247            verbose = b;
248        }
249    
250        /**
251         * Returns the {@link jgb.builder.TagHandler TagHandler} associated
252         * with the named tag.
253         * This method uses the {@link jgb.builder.TagHandlerFactory TagHandlerFactory} that
254         * was passed-in during instantiation to get the proper tag.
255         * @throws SAXException If a handler for the named tag cannot be found.
256         */
257        private TagHandler getHandlerFor(String qName) throws SAXException {
258            Stack handlersFactory = (Stack)tagContext.get(TagHandler.TAG_HANDLER_FACTORY_STACK_KEY);
259            if (handlersFactory.isEmpty()) {
260                EmptyStackException e = new EmptyStackException();
261                throw new SAXException("stack empty while processing <" + qName + ">", e);
262            }
263    
264            TagHandlerFactory factory = (TagHandlerFactory)handlersFactory.peek();
265            try {
266                return factory.getHandlerFor(qName);
267            } catch (Exception e) {
268                throw new SAXException("Could not instantiate TagHandler for tag " + qName + ".", e);
269            }
270        }
271    
272        private String levelAsSpaces() {
273            StringBuffer buf = new StringBuffer();
274            for (int i = 0; i < level; i++) {
275                buf.append("  ");
276            }
277    
278            return buf.toString();
279        }
280    
281        public void addResolver(EntityResolver resolver) {
282            resolvers.add(resolver);
283        }
284    
285        public InputSource resolveEntity(String publicId, String systemId) throws SAXException {
286            try {
287                for (Iterator iterator = resolvers.iterator(); iterator.hasNext();) {
288                    EntityResolver resolver = (EntityResolver)iterator.next();
289                    InputSource entity = resolver.resolveEntity(publicId, systemId);
290                    if (null != entity) {
291                        return entity;
292                    }
293                }
294    
295                return super.resolveEntity(publicId, systemId);
296            } catch (IOException e) {
297                throw new SAXException(
298                        "Unable to resolve '" + publicId + "' " +
299                        "('" + systemId + "')", e);
300            }
301        }
302    }